package lang

import (
	"jvmgo/instructions/base"
	"jvmgo/native"
	"jvmgo/rtda"
	"jvmgo/rtda/heap"
	"strings"
	"unsafe"
)

func init() {
	native.Register("java/lang/Class", "getPrimitiveClass", "(Ljava/lang/String;)Ljava/lang/Class;", getPrimitiveClass)
	native.Register("java/lang/Class", "getName0", "()Ljava/lang/String;", getName0)
	native.Register("java/lang/Class", "desiredAssertionStatus0", "(Ljava/lang/Class;)Z", desiredAssertionStatus0)
	native.Register("java/lang/Class", "forName0", "(Ljava/lang/String;ZLjava/lang/ClassLoader;Ljava/lang/Class;)Ljava/lang/Class;", forName0)
	native.Register("java/lang/Class", "getDeclaredFields0", "(Z)[Ljava/lang/reflect/Field;", getDeclaredFields0)
	native.Register("java/lang/Class", "isPrimitive", "()Z", isPrimitive)
	native.Register("java/lang/Class", "isAssignableFrom", "(Ljava/lang/Class;)Z", isAssignableFrom)
	native.Register("java/lang/Class", "isInterface", "()Z", IsInterface)
	native.Register("java/lang/Class", "getDeclaredConstructors0", "(Z)[Ljava/lang/reflect/Constructor;", getDeclaredConstructors0)
	native.Register("java/lang/Class", "getModifiers", "()I", getModifiers)
	native.Register("java/lang/Class", "getSuperclass", "()Ljava/lang/Class;", getSuperclass)
	native.Register("java/lang/Class", "getInterfaces0", "()[Ljava/lang/Class", getInterfaces0)
	native.Register("java/lang/Class", "isArray", "()Z", isArray)
	native.Register("java/lang/Class", "getDeclaredMethods0", "(Z)[Ljava/lang/reflect/Method;", getDeclaredMethods0)
	native.Register("java/lang/Class", "getComponentType", "()Ljava/lang/Class;", getComponentType)

}

func getComponentType(frame *rtda.Frame) {
	vars := frame.LocalVars()
	this := vars.GetThis()
	class := this.Extra().(*heap.Class)
	componentClass := class.ComponentClass()
	componentClassObj := componentClass.JClass()
	stack := frame.OperandStack()
	stack.PushRef(componentClassObj)
}

const _methodConstructorDescriptor = "(Ljava/lang/Class;Ljava/lang/Class;[Ljava/lang/Class;Ljava/lang/Class;[Ljava/lang/Class;IILjava/lang/String;[B[B[B)V"

func getDeclaredMethods0(frame *rtda.Frame) {
	vars := frame.LocalVars()
	this := vars.GetThis()
	publicOnly := vars.GetBoolean(1)
	class := this.Extra().(*heap.Class)
	methods := class.GetMethods(publicOnly)
	methodCount := uint(len(methods))
	classLoader := class.Loader()
	methodClass := classLoader.LoadClass("java/lang/reflect/Method")
	methodArr := methodClass.ArrayClass().NewArray(methodCount)
	stack := frame.OperandStack()
	stack.PushRef(methodArr)
	if methodCount > 0 {
		thread := frame.Thread()
		methodObjs := methodArr.Refs()
		methodConstructor := methodClass.GetConstructor(_methodConstructorDescriptor)
		for i, method := range methods {
			methodObj := methodClass.NewObject()
			methodObj.SetExtra(method)
			methodObjs[i] = methodObj
			ops := rtda.NewOperandStack(12)
			ops.PushRef(methodObj)
			ops.PushRef(this)
			ops.PushRef(heap.JString(classLoader, method.Name()))
			ops.PushRef(toClassArr(classLoader, method.ParameterTypes()))
			ops.PushRef(method.ReturnType().JClass())
			ops.PushRef(toClassArr(classLoader, method.ExceptionTypes()))
			ops.PushInt(int32(method.AccessFlags()))
			ops.PushInt(int32(0))
			ops.PushRef(getSignatureStr(classLoader, method.Signature()))
			ops.PushRef(toByteArr(classLoader, method.AnnotationData()))
			ops.PushRef(toByteArr(classLoader, method.ParameterAnnotationData()))
			ops.PushRef(toByteArr(classLoader, method.AnnotationDefaultData()))

			shimFrame := rtda.NewShimFrame(thread, ops)
			thread.PushFrame(shimFrame)
			base.InvokeMethod(shimFrame, methodConstructor)
		}
	}

}

func isArray(frame *rtda.Frame) {
	vars := frame.LocalVars()
	this := vars.GetThis()
	class := this.Extra().(*heap.Class)
	frame.OperandStack().PushBoolean(class.IsArray())
}

func getInterfaces0(frame *rtda.Frame) {
	vars := frame.LocalVars()
	this := vars.GetThis()
	class := this.Extra().(*heap.Class)
	interfaces := class.Interfaces()
	classArr := toClassArr(class.Loader(), interfaces)
	frame.OperandStack().PushRef(classArr)
}

func getSuperclass(frame *rtda.Frame) {
	vars := frame.LocalVars()
	this := vars.GetThis()
	class := this.Extra().(*heap.Class)
	superClass := class.SuperClass()
	stack := frame.OperandStack()
	if superClass != nil {
		stack.PushRef(superClass.JClass())
	} else {
		stack.PushRef(nil)
	}
}

func getModifiers(frame *rtda.Frame) {
	vars := frame.LocalVars()
	this := vars.GetThis()
	class := this.Extra().(*heap.Class)
	modifiers := class.AccessFlags()
	stack := frame.OperandStack()
	stack.PushInt(int32(modifiers))
}

func forName0(frame *rtda.Frame) {
	vars := frame.LocalVars()
	jName := vars.GetRef(0)
	initialize := vars.GetBoolean(1)
	goName := heap.GoString(jName)
	goName = strings.Replace(goName, ".", "/", -1)
	goClass := frame.Method().Class().Loader().LoadClass(goName)
	jClass := goClass.JClass()
	if initialize && !goClass.InitStarted() {
		thread := frame.Thread()
		frame.SetNextPC(thread.PC())
		base.InitClass(thread, goClass)
	} else {
		stack := frame.OperandStack()
		stack.PushRef(jClass)
	}
}

func getPrimitiveClass(frame *rtda.Frame) {
	nameObj := frame.LocalVars().GetRef(0)
	name := heap.GoString(nameObj)
	loader := frame.Method().Class().Loader()
	class := loader.LoadClass(name).JClass()
	frame.OperandStack().PushRef(class)
}

func getName0(frame *rtda.Frame) {
	this := frame.LocalVars().GetThis()
	class := this.Extra().(*heap.Class)
	name := class.JavaName()
	nameObj := heap.JString(class.Loader(), name)
	frame.OperandStack().PushRef(nameObj)
}

func desiredAssertionStatus0(frame *rtda.Frame) {
	frame.OperandStack().PushBoolean(false)
}

const _fieldConstructorDescriptor = "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;IILjava/lang/String;[B)V"

func getDeclaredFields0(frame *rtda.Frame) {
	vars := frame.LocalVars()
	classObj := vars.GetThis()
	publicOnly := vars.GetBoolean(1)
	class := classObj.Extra().(*heap.Class)
	fields := class.GetFields(publicOnly)
	fieldCount := uint(len(fields))
	classLoader := frame.Method().Class().Loader()
	fieldClass := classLoader.LoadClass("java/lang/reflect/Field")
	fieldArr := fieldClass.ArrayClass().NewArray(fieldCount)
	stack := frame.OperandStack()
	stack.PushRef(fieldArr)

	if fieldCount > 0 {
		thread := frame.Thread()
		fieldObjs := fieldArr.Refs()
		fieldConstructor := fieldClass.GetConstructor(_fieldConstructorDescriptor)
		for i, goField := range fields {
			fieldObj := fieldClass.NewObject()
			fieldObj.SetExtra(goField)
			fieldObjs[i] = fieldObj
			ops := rtda.NewOperandStack(8)
			ops.PushRef(fieldObj)
			ops.PushRef(classObj)
			ops.PushRef(heap.JString(classLoader, goField.Name()))
			ops.PushRef(goField.Type().JClass())
			ops.PushInt(int32(goField.AccessFlags()))
			ops.PushInt(int32(goField.SlotId()))
			ops.PushRef(getSignatureStr(classLoader, goField.Signature()))
			ops.PushRef(toByteArr(classLoader, goField.AnnotationData()))
			shimFrame := rtda.NewShimFrame(thread, ops)
			thread.PushFrame(shimFrame)
			base.InvokeMethod(shimFrame, fieldConstructor)

		}
	}
}

func toByteArr(loader *heap.ClassLoader, goBytes []byte) *heap.Object {
	if goBytes != nil {
		jBytes := castUint8sToInt8s(goBytes)
		return heap.NewByteArray(loader, jBytes)
	}
	return nil
}

func castUint8sToInt8s(bytes []byte) []int8 {
	ptr := unsafe.Pointer(&bytes)
	jBytes := *((*[]int8)(ptr))
	return jBytes
}

func getSignatureStr(loader *heap.ClassLoader, signature string) *heap.Object {
	if signature != "" {
		return heap.JString(loader, signature)
	}
	return nil
}

func isPrimitive(frame *rtda.Frame) {
	vars := frame.LocalVars()
	this := vars.GetThis()
	class := this.Extra().(*heap.Class)
	stack := frame.OperandStack()
	stack.PushBoolean(class.IsPrimitive())
}

func isAssignableFrom(frame *rtda.Frame) {
	vars := frame.LocalVars()
	this := vars.GetThis()
	cls := vars.GetRef(1)
	thisClass := this.Extra().(*heap.Class)
	clsClass := cls.Extra().(*heap.Class)
	ok := thisClass.IsAssignableFrom(clsClass)
	stack := frame.OperandStack()
	stack.PushBoolean(ok)
}

func IsInterface(frame *rtda.Frame) {
	vars := frame.LocalVars()
	this := vars.GetThis()
	class := this.Extra().(*heap.Class)
	stack := frame.OperandStack()
	stack.PushBoolean(class.IsInterface())
}

const _constructorConstructorDescriptor = "(Ljava/lang/Class;[Ljava/lang/Class;[Ljava/lang/Class;IILjava/lang/String;[B[B)V"

func getDeclaredConstructors0(frame *rtda.Frame) {
	vars := frame.LocalVars()
	classObj := vars.GetThis()
	publicOnly := vars.GetBoolean(1)
	class := classObj.Extra().(*heap.Class)
	constructors := class.GetConstructors(publicOnly)
	constructorCount := uint(len(constructors))
	classLoader := frame.Method().Class().Loader()
	constructorClass := classLoader.LoadClass("java/lang/reflect/Constructor")
	constructorArr := constructorClass.ArrayClass().NewArray(constructorCount)
	stack := frame.OperandStack()
	stack.PushRef(constructorArr)

	if constructorCount > 0 {
		thread := frame.Thread()
		constuctorObjs := constructorArr.Refs()
		constructorInitMethod := constructorClass.GetConstructor(_constructorConstructorDescriptor)
		for i, constructor := range constructors {
			constructorObj := constructorClass.NewObject()
			constructorObj.SetExtra(constructor)
			constuctorObjs[i] = constructorObj
			ops := rtda.NewOperandStack(9)
			ops.PushRef(constructorObj)
			ops.PushRef(classObj)
			ops.PushRef(toClassArr(classLoader, constructor.ParameterTypes()))
			ops.PushRef(toClassArr(classLoader, constructor.ExceptionTypes()))
			ops.PushInt(int32(constructor.AccessFlags()))
			ops.PushInt(int32(0))
			ops.PushRef(getSignatureStr(classLoader, constructor.Signature()))
			ops.PushRef(toByteArr(classLoader, constructor.AnnotationData()))
			ops.PushRef(toByteArr(classLoader, constructor.ParameterAnnotationData()))

			shimFrame := rtda.NewShimFrame(thread, ops)
			thread.PushFrame(shimFrame)
			base.InvokeMethod(shimFrame, constructorInitMethod)
		}
	}

}

func toClassArr(loader *heap.ClassLoader, classes []*heap.Class) *heap.Object {
	arrLen := len(classes)
	classArrClass := loader.LoadClass("java/lang/Class").ArrayClass()
	classArr := classArrClass.NewArray(uint(arrLen))
	if arrLen > 0 {
		classObjs := classArr.Refs()
		for i, class := range classes {
			classObjs[i] = class.JClass()
		}
	}
	return classArr
}
